#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <assert.h>
#include <dlfcn.h>
#include <ctype.h>
#include <readline/readline.h>
#include <readline/history.h>

typedef int (*Fn)(int, ...);

static char *line;
static char funcname[128] = {0};
static char errbuf[128] = {0};
static int 	cnt_func = 0;

static void parseline() {
  memset(funcname, 0, sizeof(funcname));
  memset(errbuf, 0, sizeof(errbuf));
  if (strlen(line) == 0)
    return;
  int isfunc = strncmp(line, "int", 3) == 0;
  if (!isfunc) {
    sprintf(funcname, "%s%d", "__expr_func_", cnt_func++);
  } else {
    char *f = funcname;
    char *p = line + 3;
    for (; *p != '(' && *p != 0; p++) {
      if (!isspace(*p))
        *f++ = *p;
    }
  }
  char oldname[] = "/tmp/creplXXXXXX";
  char newname[32] = {0};
  char libname[32] = {0};
  int tmpfd = mkstemp(oldname);
  assert(tmpfd);
  sprintf(newname, "%s.c", oldname);
  rename(oldname, newname);
  sprintf(libname, "/tmp/lib%s.so", oldname + 5);
  int fd[2];
  assert(pipe(fd) == 0);
  pid_t pid = fork();
  if (pid == 0) {
    close(fd[0]);
    dup2(fd[1], STDERR_FILENO);
    char *sz = sizeof(size_t) == 4 ? "-m32" : "-m64";
    char *argv[] = { "gcc", "-w", "-fPIC", "-shared", sz, newname, "-o", libname, NULL };
    extern char **environ;
    if (isfunc) {
      assert(write(tmpfd, line, strlen(line)) != -1);
      execve("/usr/bin/gcc", argv, environ);
      exit(-1);
    } else {
      assert(write(tmpfd, "int ", 4) != -1);
      assert(write(tmpfd, funcname, strlen(funcname)) != -1);
      assert(write(tmpfd, "(int pid) { return ", 19) != -1);
      assert(write(tmpfd, line, strlen(line)) != -1);
      assert(write(tmpfd, "; }", 3) != -1);
      execve("/usr/bin/gcc", argv, environ);
      exit(-1);
    }
  } else {
    close(fd[1]);
    assert(wait(NULL) == pid);
    read(fd[0], errbuf, sizeof(errbuf));
    if (strlen(errbuf) != 0) {
      printf("compile error\n");
      assert(unlink(newname) == 0);
      close(tmpfd);
      return;
    }
    void *handle = dlopen(libname, RTLD_LAZY | RTLD_GLOBAL);
    assert(handle);
    Fn fn = dlsym(handle, funcname);
    assert(fn);
    if (!isfunc) {
      printf("(%s) == %d\n", line, fn(pid));
    }
    assert(unlink(libname) == 0);
    assert(unlink(newname) == 0);
    close(tmpfd);
  }
}

static void rl_gets() {
  if (line) {
    free(line);
    line = NULL;
  }
  line = readline("crepl> ");
  if (line && *line) {
    add_history(line);
  }
}

int main() {
  while (1) {
    rl_gets();
    if (strcmp(line, "quit") == 0 || strcmp(line, "q") == 0)
      break;
    parseline();
  }

  return 0;
}